package com.gitee.maskit.web;

import com.gitee.maskit.utils.Response;
import com.gitee.maskit.utils.ResponseAdapter;
import org.springframework.context.MessageSource;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Order(Ordered.HIGHEST_PRECEDENCE)
public class ExceptionResolver extends ExceptionHandlerExceptionResolver {
    private static final ModelAndView HANDLED = new ModelAndView();
    private final boolean forceHandle;
    private final ResponseAdapter<Object> adapter;
    private final Map<Class, Integer> codeExceptions;
    private final MessageSource source;
    private final MappingJackson2HttpMessageConverter converter;

    public ExceptionResolver(boolean forceHandle, ResponseAdapter<Object> adapter, Map<Integer, Class> codeExceptions, MessageSource source, MappingJackson2HttpMessageConverter converter) {
        this.forceHandle = forceHandle;
        this.adapter = adapter;
        this.codeExceptions = new HashMap<Class, Integer>();
        if (null != codeExceptions) {
            for (Map.Entry<Integer, Class> entry : codeExceptions.entrySet()) {
                this.codeExceptions.put(entry.getValue(), entry.getKey());
            }
        }
        this.source = source;
        this.converter = converter;
    }

    @Override
    protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) {
        if (forceHandle || Headers.XRequestedWith.match(request)) {
            if (null != exception) {
                final Class exceptionClass = exception.getClass();
                final ServletServerHttpResponse servletServerHttpResponse = new ServletServerHttpResponse(response);
                response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                try {
                    response.setStatus(
                        codeExceptions.getOrDefault(exceptionClass,
                            Response.GENERAL_FAILURE_STATUS));
                    Response result = Response.no(response.getStatus(),
                        exception.getMessage());

                    if (null != source) {
                        result.setMessage(source.getMessage(
                            result.getError(), null,
                            result.getError(), WebHolder.LOCALE.get()));
                    } else {
                        result.setMessage(result.getError());
                    }

                    if (null != adapter)
                        converter.write(adapter.apply(result),
                            MediaType.APPLICATION_JSON_UTF8,
                            servletServerHttpResponse);
                    else
                        converter.write(result,
                            MediaType.APPLICATION_JSON_UTF8,
                            servletServerHttpResponse);

                    logger.info(exception);
                    return HANDLED;
                } catch (IOException e) {
                    logger.warn("Unable to response with " +
                        MediaType.APPLICATION_JSON_UTF8_VALUE + " for ", e);
                }
            }
        }
        return super.doResolveHandlerMethodException(request, response, handlerMethod, exception);
    }

    private enum Headers {
        XRequestedWith("X-Requested-With", "XMLHttpRequest");
        private final String header;
        private final String value;

        Headers(String header, String value) {
            this.header = header;
            this.value = value;
        }

        public boolean match(final HttpServletRequest request) {
            String header;
            return null != (header = request.getHeader(this.header)) && this.value.equalsIgnoreCase(header);
        }
    }
}
